﻿using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.ServiceModel;
using System.Xml.Linq;
using CoreEx.Common.Reflection;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;
using CoreEx.Common.Extensions;
namespace CoreEx.Wcf.Common.Implementation
{
    /// <summary>
    /// Creates an interface that is compliant with the requriments of an WCF service contract.
    /// </summary>
    [Implements(typeof(IServiceContractBuilder),LifecycleType.Singleton, ServiceName = "ServiceContractBuilder")]    
    public class ServiceContractBuilder : IServiceContractBuilder,IInitialize
    {
        private const string _assemblyPrefix = "ServiceContract";

        private IServiceContainer _serviceContainer;


        /// <summary>
        /// Creates a WCF service contract.
        /// </summary>        
        /// <remarks>
        /// The <see cref="ServiceContractAttribute"/> attribute will be applied to the new type 
        /// and the <see cref="OperationContractAttribute"/> will be applied to all its members.
        /// </remarks>
        /// <param name="type">The source interface for witch to create the service contract.</param>
        /// <returns>An interface representing a WCF service contract</returns>        
        public Type CreateServiceContract(Type type)
        {
            return CreateServiceContract(type, true);            
        }


        /// <summary>
        /// Creates a WCF service contract.
        /// </summary>
        /// <param name="type">The source interface for witch to create the service contract.</param>
        /// <param name="createAttributes">Indicates if the <see cref="ServiceContractAttribute"/> 
        /// and the <see cref="OperationContractAttribute"/> should be added to the new type.</param>
        /// <returns>An interface representing a WCF service contract</returns>        
        public Type CreateServiceContract(Type type, bool createAttributes)
        {
            
            //Experimental

            

            var typeDescriptor = _serviceContainer.GetService<ITypeDescriptor>(type);

                                                
            //Create a new unique assembly name
            var assemblyName = new AssemblyName(_assemblyPrefix + Guid.NewGuid());
            
            //Create a new assembly builder that represents the new dynamic assembly.
            AssemblyBuilder assemblyBuilder =
                AppDomain.CurrentDomain.DefineDynamicAssembly(
                    assemblyName,
                    AssemblyBuilderAccess.Run);

            //Create a new module within the assembly
            ModuleBuilder mb =
                assemblyBuilder.DefineDynamicModule(assemblyName.Name + ".dll");

            //Create a new type that has the same name as the source type
            TypeBuilder typeBuilder = mb.DefineType(type.FullName, type.Attributes);

            //Check to see if the service contract attribute should be applied
            if (createAttributes)
            {
                var serviceContractConstructor = typeof (ServiceContractAttribute).GetConstructor(new Type[] {});
                CustomAttributeBuilder cb = new CustomAttributeBuilder(serviceContractConstructor, new object[] {});
                typeBuilder.SetCustomAttribute(cb);
            }

            var sourceMethods = typeDescriptor.GetMethods();
            

            var operationContractConstructor = typeof(OperationContractAttribute).GetConstructor(new Type[] { });

            foreach (var methodInfo in sourceMethods)
            {
                Type returnType;
                if (methodInfo.ReturnType.ContainsGenericParameters)
                    if (methodInfo.ReturnType.IsEnumerable())
                        returnType = typeof(IList);
                    else
                        returnType = typeof(object);
                else
                    returnType = methodInfo.ReturnType;
                
                
                var parameterTypes = methodInfo.GetParameters().Cast<ParameterInfo>().Select(p => p.ParameterType).ToArray();
                //Search for Expression parameters and replace it with a XElement
                var actualParameterTypes = parameterTypes.Select(p => typeof(Expression).IsAssignableFrom(p) ? typeof(XElement) : p).ToArray();
                
                
                var methodBuilder = typeBuilder.DefineMethod(methodInfo.Name, methodInfo.Attributes, methodInfo.CallingConvention, returnType, actualParameterTypes);
                var parameterInfoList = methodInfo.GetParameters();
                foreach (var parameterInfo in parameterInfoList)
                {
                    //The first parameters is at index 1, index 0 represents the return value
                    methodBuilder.DefineParameter(parameterInfo.Position + 1, parameterInfo.Attributes, parameterInfo.Name);
                }
                if (createAttributes)
                {
                    var nameProperty = typeof (OperationContractAttribute).GetProperty("Name");
                    var actionProperty = typeof (OperationContractAttribute).GetProperty("Action");
                    var replyActionProperty = typeof (OperationContractAttribute).GetProperty("ReplyAction");

                    var nameValue = string.Format("{0}.{1}", type.FullName, methodInfo.Name);
                    var actionValue = string.Format("{0}.{1}", type.FullName, methodInfo.Name);
                    var replyActionValue = string.Format("{0}.ResponseTo{1}", type.FullName, methodInfo.Name);
                    var propertyList = new PropertyInfo[] {nameProperty, actionProperty, replyActionProperty};
                    var valueList = new string[] {nameValue, actionValue, replyActionValue};
                    CustomAttributeBuilder customAttributeBuilder =
                        new CustomAttributeBuilder(operationContractConstructor, new object[] {}, propertyList,
                                                   valueList);
                    methodBuilder.SetCustomAttribute(customAttributeBuilder);
                }
            }

            Type test = typeBuilder.CreateType();
            //assemblyBuilder.Save(@"Test.dll");

            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            return test;

        }

        #region IInitialize<IServiceContainer> Members

        public void Initialize(IServiceContainer source)
        {
            _serviceContainer = source;
            
            //Even though the new assembly is loaded into the AppDomain, 
            //it will not be found when the runtime is searching for the new assembly.
            //We need to intercept the request for the dynamic assembly and return the corresponding assembly.
            AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
                                                           {
                                                               if (!args.Name.StartsWith(_assemblyPrefix))
                                                                   return null;

                                                               var assemblies = AppDomain.CurrentDomain.GetAssemblies();
                                                               var assembly =
                                                                   assemblies.Where(a => a.FullName == args.Name).
                                                                       FirstOrDefault();
                                                               return assembly;
                                                           };
        }

        
        #endregion
    }
}